package com.jidong.test_springboot.shiro;

import java.util.Set;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AccountException;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.jidong.test_springboot.dao.UserDao;
import com.jidong.test_springboot.entity.User;
import com.jidong.test_springboot.service.UserService;

import lombok.extern.slf4j.Slf4j;

@Component  //该注解是生成一个bean交个spring管理，这个bean会注入给shiroConfig配置文件中的 SecurityManager
@Slf4j
public class ShiroRealm  extends AuthorizingRealm{
	
	@Autowired
	private UserDao userDao;
	
	@Autowired
	@Lazy 
	private UserService userService;
	
	/**
	 * 必须重写此方法，因为authc拦截器默认是UsernamePasswordToken
	 */
	@Override
	public boolean supports(AuthenticationToken token) {
		return token instanceof JwtToken;
	}
	
	
	
	/*这个方法是用于角色验证和权限验证的方法，该方法不需要我们调用，shiro会在经历拦截链(map)自动调用
	 * 该方法只做一件事，就是将某用户的所有角色和所有权限获得，交给shiro 方便连接链判断
	 * 
	 * ****/
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		log.info("-----------加载权限验证需要的数据库依据数据  包括角色和权限两个验证信息，--------------------------------");
		SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
		//把当前用户的所有角色和权限从数据库获取并交给info
		//获取当前用户
		String uname=(String)SecurityUtils.getSubject().getPrincipal();//
		//这内容是uname，取决于身份验证方法的AuthenticationInfo对象,info对象中构造方法的第一个参数我们传的是用户名
		Set<String> roles=userService.selectRoleByUname(uname);
		Set<String> permisssions=userService.selectPermissionByUname(uname);
		info.setRoles(roles);
		info.setStringPermissions(permisssions);
		return info;
	}

	
	/* 这个方法是用于用户登录判断的。如果用户登录失败 将抛出异常，没有登录则进入noLogin.do***/
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		log.info("-----------判断用户登录逻辑，，如果没有登录 会进入noLogin，如果登录失败会抛出异常，--------------------------------");
		//把token转为用户名密码token
		//UsernamePasswordToken tk=(UsernamePasswordToken)token;
		JwtToken tk=(JwtToken)token;
		
		boolean isTimeout=tk.isTimeout();
		if(isTimeout) {
			throw new AccountException("token过期，请重新登录");
		}
		
		//验证token的有效性，把用户名和密码再重新拿出来 看用户名是否有值就行了，没必要入库检查了。然后再拿着密码重新生成秘钥签名然后再验证token是否有效即可
		String username=(String)tk.getPrincipal();
		if(username==null) {
			throw new AccountException("token非法，请重新登录");
		}
		String password=(String)tk.getCredentials();
		Algorithm algo=Algorithm.HMAC256(password);//生成一个签名
		
		try {//根据新生成的签名验证一下token是否有效  如果验证不通过则抛出异常
			JWT.require(algo).build().verify(tk.getToken());
		} catch (JWTVerificationException e) {
			throw new AccountException("token非法，请重新登录");
		}
		
		//如果快过期了怎么办？我们现在系统有问题，一个用户只能登陆15分钟，每次访问应该是重新计算时间。到最后一次访问再延长15分钟，才对
		// 解决思路是重新生成一个token，是带着新时间的token，把这个token再给前端
		//这段代码不能写在这，因为没有request和response对象
		
		return new SimpleAuthenticationInfo(tk.getPrincipal(),tk.getCredentials(),this.getName());
		
	}

}
